﻿using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace CommonLibrary.SocketUtils
{
    public class SocketServer : IDisposable
    {
        private Socket InternelSocket;
        private Func<SocketReceiveData, bool> DataHandler;
        private SocketReceiveData State;
        private System.Threading.CancellationTokenSource CancelSource;
        private ProtocolType Type = ProtocolType.Tcp;

        /// <summary>
        /// 缓存多个客户端
        /// </summary>
        private ConcurrentDictionary<string, Socket> ClientDic;

        public volatile bool Listenning;

        public SocketServer(Func<SocketReceiveData, bool> action, ProtocolType protocolType = ProtocolType.Tcp)
        {
            Type = protocolType;
            ClientDic = new ConcurrentDictionary<string, Socket>(2, 8);
            SocketType type = SocketType.Unknown;
            if (protocolType == ProtocolType.Tcp)
                type = SocketType.Stream;
            else if (protocolType == ProtocolType.Udp)
                type = SocketType.Dgram;

            InternelSocket = new Socket(type, protocolType);
            DataHandler = action;
            State = new SocketReceiveData();
        }

        public void Start(int port)
        {
            if (Listenning)
            {
                return;
            }

            if (Type == ProtocolType.Tcp)
                StartListenningTcp(port);
            else if (Type == ProtocolType.Udp)
                StartListenningUdp(port);
            //CommonLog.LogInfo($"本地监听({port})端口成功!");
        }

        /// <summary>
        /// 开始监听Tcp
        /// </summary>
        /// <param name="port"></param>
        private void StartListenningTcp(int port)
        {
            var ep = new IPEndPoint(IPAddress.Any, port);
            InternelSocket.Bind(ep);
            InternelSocket.Listen(2);
            Listenning = true;
            CancelSource = new System.Threading.CancellationTokenSource();
            Task.Factory.StartNew(AcceptClient, CancelSource.Token);
        }

        private void StartListenningUdp(int port)
        {
            var ep = new IPEndPoint(IPAddress.Any, port);
            InternelSocket.Bind(ep);
            Listenning = true;
            UdpBeginReceive(InternelSocket);
        }

        public void Stop()
        {
            Listenning = false;
            if (CancelSource != null)
            {
                if (CancelSource.Token.CanBeCanceled)
                    CancelSource.Cancel();

                CancelSource.Dispose();
                CancelSource = null;
            }
        }

        #region tcp receive

        public async Task AcceptClient()
        {
            while (Listenning)
            {
                Socket client = await InternelSocket.AcceptAsync();
                string ip = client.RemoteEndPoint.ToString();
                if (ClientDic.TryRemove(ip, out var removeObj))
                {
                    removeObj.Dispose();
                    removeObj = null;
                }
                if (ClientDic.TryAdd(ip, client))
                {
                    client.ReceiveBufferSize = 1024 * 512;
                    client.ReceiveTimeout = -1;
                    TcpBeginReceive(client);
                }
                //CommonLog.LogInfo($"接收到新客户端:{ip}");
            }
        }

        private void TcpBeginReceive(Socket client)
        {
            try
            {
                State.SocketClient = client;
                client.BeginReceive(State.Buffer, 0, SocketReceiveData.BufferSize, 0, TcpReceiveCallback, State);
            }
            catch (Exception ex)
            {
                throw;//CommonLog.LogError(ex, "Receive(Socket client) Error =>");
            }
        }

        private void TcpReceiveCallback(IAsyncResult ar)
        {
            try
            {
                if (ar == null || ar.AsyncState == null)
                    return;

                SocketReceiveData receivedData = (SocketReceiveData)ar.AsyncState;
                Socket client = receivedData.SocketClient;

                int receiveLen = client.EndReceive(ar);
                bool effectiveCallback = receiveLen > 0;
                if (effectiveCallback)
                {
                    receivedData.Length = receiveLen;
                    if (DataHandler != null)
                    {
                        bool exeRs = DataHandler.Invoke(receivedData);
                    }
                }
                //CommonLog.LogDebug($"received tcp data length = {receiveLen}");

                if (Listenning && effectiveCallback)
                    client.BeginReceive(receivedData.Buffer, 0, SocketReceiveData.BufferSize, 0, new AsyncCallback(TcpReceiveCallback), receivedData);
                else if (!effectiveCallback)
                {
                    string ip = client.RemoteEndPoint.ToString();
                    ClientDic.TryRemove(ip, out Socket removeClient);
                    client.Dispose();
                }
            }
            catch (Exception ex)
            {
                //CommonLog.LogError(ex, "tcp ReceiveCallback(IAsyncResult ar) Error =>");
            }
        }

        #endregion

        #region udp receive

        private void UdpBeginReceive(Socket socket)
        {
            try
            {
                State.SocketClient = socket;
                EndPoint endPoint = socket.LocalEndPoint;

                socket.BeginReceiveFrom(State.Buffer, 0, SocketReceiveData.BufferSize, 0, ref endPoint, UdpReceiveCallback, State);
            }
            catch (Exception ex)
            {
                //CommonLog.LogError(ex, "UdpBeginReceive(IAsyncResult ar) Error =>");
            }
        }

        private void UdpReceiveCallback(IAsyncResult ar)
        {
            SocketReceiveData receiveData = (SocketReceiveData)ar.AsyncState;
            EndPoint endPoint = receiveData.SocketClient.LocalEndPoint;
            try
            {
                State.Length = InternelSocket.EndReceiveFrom(ar, ref endPoint);
                bool effectiveCallback = State.Length > 0;
                if (effectiveCallback)
                {
                    if (DataHandler != null)
                    {
                        State.RemoteEndPoint = endPoint;
                        bool exeRs = DataHandler.Invoke(State);
                    }
                }
                //CommonLog.LogDebug($"udp received data length = {State.Length}");

                if (Listenning)
                    InternelSocket.BeginReceiveFrom(State.Buffer, 0, SocketReceiveData.BufferSize, 0, ref endPoint, new AsyncCallback(UdpReceiveCallback), State);
            }
            catch (Exception ex)
            {
                //CommonLog.LogError(ex, "UdpReceiveCallback(IAsyncResult ar) Error =>");
            }
        }

        #endregion

        #region Dispose

        private bool disposedValue;
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    InternelSocket.Dispose();
                    State?.Dispose();
                }
                disposedValue = true;
            }
        }

        public void Dispose()
        {
            // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
        #endregion

    }

}